/*****************************************************************
Mix3D WebGL Game Engine
Copyright (c) fantasy ( fantasy.x2x@gmail.com )
Listens:
Distributed under the Boost Software License, Version 1.0.
http://www.boost.org/LICENSE_1_0.txt
 ****************************************************************/
/**
 * @Desc    : Quaternion
 * @Author  : fantasy(fantasy.x2x@gmail.com)
 */
//////////////////////////////////////////////////////////////////
if(typeof(Mix3D) === 'undefined')
{
  Mix3D = {};
};
(function(Mix3D){
//////////////////////////////////////////////////////////////////
Mix3D.Quaternion = function( x, y, z, w )
{
  Mix3D.FloatArray.call(this, 4);
  this.FA[0] = ( w !== undefined ) ? w : 1;
  this.FA[1] = x || 0;
  this.FA[2] = y || 0;
  this.FA[3] = z || 0;
};


Mix3D.Quaternion.prototype.constructor = Mix3D.Quaternion;
Mix3D.Quaternion.prototype.w = function() { return this.FA[0]; };
Mix3D.Quaternion.prototype.x = function() { return this.FA[1]; };
Mix3D.Quaternion.prototype.y = function() { return this.FA[2]; };
Mix3D.Quaternion.prototype.z = function() { return this.FA[3]; };

Mix3D.Quaternion.prototype.set = function ( x, y, z, w ) 
{
  this.FA[0] = w;
  this.FA[1] = x;
  this.FA[2] = y;
  this.FA[3] = z;
  return this;
};

Mix3D.Quaternion.prototype.fromEuler = function( xDegree, yDegree, zDegree)
{
  var xRed = Mix3D.Math.DegToRad(xDegree);
  var yRad = Mix3D.Math.DegToRad(yDegree);
  var zRad = Mix3D.Math.DegToRad(zDegree);

  c1 = Math.cos( yRad  ),
  s1 = Math.sin( yRad  ),
  c2 = Math.cos( -zRad ),
  s2 = Math.sin( -zRad ),
  c3 = Math.cos( xRad  ),
  s3 = Math.sin( xRad  ),

  c1c2 = c1 * c2,
  s1s2 = s1 * s2;

  this.FA[0] = c1c2 * c3  - s1s2 * s3;
  this.FA[1] = c1c2 * s3  + s1s2 * c3;
  this.FA[2] = s1 * c2 * c3 + c1 * s2 * s3;
  this.FA[3] = c1 * s2 * c3 - s1 * c2 * s3;

  return this;
};

Mix3D.Quaternion.prototype.fromAxisAngle = function ( axis, radian ) 
{
  var halfAngle = angle *0.5,
  sn = Math.sin( halfAngle );
  this.FA[0] = Math.cos( halfAngle );
  this.FA[1] = axis.x * sn;
  this.FA[2] = axis.y * sn;
  this.FA[3] = axis.z * sn;
  return this;
};

Mix3D.Quaternion.prototype.fromRotMatrix = function ( m )
{
  function copySign( a, b ) 
  {
    return b < 0 ? -Math.abs( a ) : Math.abs( a );
  }

  var absQ = Math.pow( m.determinant(), 1.0 / 3.0 );
  this.FA[0] = Math.sqrt( Math.max( 0, absQ + m.FA[0] + m.FA[5] + m.FA[10] ) ) / 2;
  this.FA[1] = Math.sqrt( Math.max( 0, absQ + m.FA[0] - m.FA[5] - m.FA[10] ) ) / 2;
  this.FA[2] = Math.sqrt( Math.max( 0, absQ - m.FA[0] + m.FA[5] - m.FA[10] ) ) / 2;
  this.FA[3] = Math.sqrt( Math.max( 0, absQ - m.FA[0] - m.FA[5] + m.FA[10] ) ) / 2;
  this.FA[1] = copySign( this.FA[1], ( m.FA[6] - m.FA[9] ) );
  this.FA[2] = copySign( this.FA[2], ( m.FA[8] - m.FA[2] ) );
  this.FA[3] = copySign( this.FA[3], ( m.FA[1] - m.FA[4] ) );
  this.normalize();

  return this;
};

Mix3D.Quaternion.prototype.formSrcDest = function( src, dest, fallbackAxis)
{
  if(fallbackAxis === undefined)fallbackAxis = Mix3D.Vector3.ZERO;
  var v0 = Mix3D.Quaternion._tempV1.copy(src).normalize();
  var v1 = Mix3D.Quaternion._tempV2.copy(dest).normalize();
  var d = v0.dot(v1);
  if( d >= 1.0)
  {
    this.set(0,0,0,1);
    return;
  }
  if( d< -0.999999)
  {
    if(fallbackAxis.isZero() === false)
    {
      this.fromAxisAngle(fallbackAxis, Mix3D.Math.PI);
    }
    else
    {
      v0.cross(Mix3D.Vector3.UNIT_X, src);
      if( v0.isZero())
      {
        v0.cross(Mix3D.Vector3.UNIT_Y, src);
      }
      v0.normalize();
      this.fromAxisAngle(v0, Math.PI);
    }
  }
  else
  {
    var s = Math.sqrt((1+d)*2);
    var invs = 1/s;
    var c = Mix3D.Quaternion._tempV3.cross(v0,v1);
    this.FA[0] = s * 0.5;
    this.FA[1] = c.x * invs;
    this.FA[2] = c.y * invs;
    this.FA[3] = c.z * invs;
    this.normalize();
  }
};

Mix3D.Quaternion.prototype.fromRotateAxis = function( ax, ay, az)
{
  this.fromRotationMatrix(Mix3D.Quaternion._tempM.identity().setAxis(ax,ay.az));
};

Mix3D.Quaternion.prototype.calculateW = function ()
{
  this.FA[0] = - Math.sqrt( Math.abs( 1.0 - this.FA[1] * this.FA[1] 
               - this.FA[2] * this.FA[2] - this.FA[3] * this.FA[3] ) );
  return this;
};

Mix3D.Quaternion.prototype.inverse = function () 
{
  this.FA[1] *= -1;
  this.FA[2] *= -1;
  this.FA[3] *= -1;
  return this;
};

Mix3D.Quaternion.prototype.len = function () 
{
  return Math.sqrt( this.FA[0] * this.FA[0] + 
                    this.FA[1] * this.FA[1] + 
                    this.FA[2] * this.FA[2] + 
                    this.FA[3] * this.FA[3] );
};

Mix3D.Quaternion.prototype.normalize = function ()
{
  var len = this.len();

  if ( len > 0 ) 
  {
    invLen = 1 / len;
    this.FA[0] *=invLen;
    this.FA[1] *=invLen;
    this.FA[2] *=invLen;
    this.FA[3] *=invLen;
  } 
  else 
  {
    this.FA[0] = 0;
    this.FA[1] = 0;
    this.FA[2] = 0;
    this.FA[3] = 0;
  }
  return this;
};

Mix3D.Quaternion.prototype.mul = function ( q1, a2 ) 
{
  var la=q1.FA, ra=q2.FA;
  this.FA[0] = la[0]*ra[0] - la[1]*ra[1] - la[2]*ra[2] - la[3]*ra[3];
  this.FA[1] = la[0]*ra[1] + la[1]*ra[0] + la[2]*ra[3] - la[3]*ra[2];
  this.FA[2] = la[0]*ra[2] + la[2]*ra[0] + la[3]*ra[1] - la[1]*ra[3];
  this.FA[3] = la[0]*ra[3] + la[3]*ra[0] + la[1]*ra[2] - la[2]*ra[1];
  return this;
};

Mix3D.Quaternion.prototype.mulSelf = function ( q )
{
  var la0 = this.FA[0], la1=this.FA[1], la2=this.FA[2], la3=this.FA[3];
  var ra = q.FA;

  this.FA[0] = la0*ra[0] - la1*ra[1] - la2*ra[2] - la3*ra[3];
  this.FA[1] = la0*ra[1] + la1*ra[0] + la2*ra[3] - la3*ra[2];
  this.FA[2] = la0*ra[2] + la2*ra[0] + la3*ra[1] - la1*ra[3];
  this.FA[3] = la0*ra[3] + la3*ra[0] + la1*ra[2] - la2*ra[1];
  return this;
};

Mix3D.Quaternion.prototype.mulVector3 = function ( vector, dest ) 
{
  if ( !dest ) { dest = vector; }
  var uv = Mix3D.Quaternion._tempV1;
  var uuv = Mix3D.Quaternion._tempV2;
  var qvec = Mix3D.Quaternion._tempV3;
  qvec.set(this.x(),this.y(),this.z());
  uv.cross(qvec, vector);
  uuv.cross(qvec, uv);
  uv.mulScalar(2.0*this.w());
  uuv.mulScalar(2.0);
  dest.add(vector, uv);
  dest.addSelf(uuv);
  return dest;
};

Mix3D.Quaternion.prototype.slerp = function( qs, qt, t)
{
  var cs = qs.FA[0] * qt.FA[0] + qs.FA[1] * qt.FA[1] + 
           qs.FA[2] * qt.FA[2] + qs.FA[3] * qt.FA[3];

  var angle = Math.acos(cs);
  
  if (cs < 0) 
  {
    this.FA[0] = -this.FA[0];
    this.FA[1] = -this.FA[1];
    this.FA[2] = -this.FA[2];
    this.FA[3] = -this.FA[3];
    cs = -cs;
  }
  else 
  {
    this.copy(qt);
  }

  if ( Math.abs( cs ) >= 1.0 ) 
  {
    this.copy(qs);
    return this;
  }

  var halfTheta = Math.acos( cs ),
  sinHalfTheta = Math.sqrt( 1.0 - cs * cs );

  if ( Math.abs( sinHalfTheta ) < 0.001 ) 
  {
    this.FA[0] = 0.5 * ( qs.FA[0] + qt.FA[0] );
    this.FA[1] = 0.5 * ( qs.FA[1] + qt.FA[1] );
    this.FA[2] = 0.5 * ( qs.FA[2] + qt.FA[2] );
    this.FA[3] = 0.5 * ( qs.FA[3] + qt.FA[3] );

    return this;
  }

  var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
  ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;

  this.FA[0] = ( qs.FA[0] * ratioA + this.FA[0] * ratioB );
  this.FA[1] = ( qs.FA[1] * ratioA + this.FA[1] * ratioB );
  this.FA[2] = ( qs.FA[2] * ratioA + this.FA[2] * ratioB );
  this.FA[3] = ( qs.FA[3] * ratioA + this.FA[3] * ratioB );

  return this;
};

Mix3D.Quaternion.prototype.squad = function(q0, a0, q1, a1, t)
{
  var st = 2*t*(1-t);
  var s1 = Mix3D.Quaternion._tempQ1.slerp(q0, q1, t);
  var s2 = Mix3D.Quaternion._tempQ2.slerp(a0, q1, t);
  return this.slerp(s1, s2, st);
};

Mix3D.Quaternion.prototype.clone = function () 
{
  return new Mix3D.Quaternion( this.x, this.y, this.z, this.w );
};
Mix3D.Quaternion._tempV1 = new Mix3D.Vector3();
Mix3D.Quaternion._tempV2 = new Mix3D.Vector3();
Mix3D.Quaternion._tempV3 = new Mix3D.Vector3();
Mix3D.Quaternion._tempQ1 = new Mix3D.Quaternion();
Mix3D.Quaternion._tempQ2 = new Mix3D.Quaternion();
Mix3D.Quaternion._tempM = new Mix3D.Matrix4();

Mix3D.protoCopy(Mix3D.FloatArray, Mix3D.Quaternion);
//////////////////////////////////////////////////////////////////
})(Mix3D);// end of function(Mix3D)